from dataclasses import dataclass
import wx

import random
import Musicreater


MAX_LENGTH = 1200

# Define grid cell dimensions
GRID_WIDTH = 3
GRID_HEIGHT = 10

# Define snap and border thresholds
SNAP_THRESHOLD = 5
BORDER_THRESHOLD = 3


@dataclass
class Rectangle:

    x_pos: int
    y_pos: int
    width_length: int
    colour: wx.Colour
    note: Musicreater.MineNote

    def __init__(self, x, y, width, colour: tuple, note_: Musicreater.MineNote):
        self.x_pos = x * GRID_WIDTH
        self.y_pos = y * GRID_HEIGHT
        self.width_length = width * GRID_WIDTH
        self.colour = wx.Colour(colour)
        self.note = note_

    @classmethod
    def from_note(cls, note: Musicreater.MineNote, colour: tuple):
        return cls(note.start_tick, note.note_pitch, note.duration, colour, note)

    @property
    def x(self):
        return round(self.x_pos / GRID_WIDTH)

    @property
    def y(self):
        return round(self.y_pos / GRID_HEIGHT)

    @property
    def width(self):
        return round(self.width_length / GRID_WIDTH)

    @property
    def pos(self):
        return wx.Point(round(self.x_pos), round(self.y_pos))

    def redefine_pos(self, pos: wx.Point):
        self.x_pos = pos.x
        self.y_pos = pos.y

    # def reset_xpos(self, x: int):
    #     self.x_pos = x

    # def reset_ypos(self, y: int):
    #     self.y_pos = y

    # def reset_width(self, width: int):
    #     self.width_length = width


class RectanglePanel(wx.ScrolledWindow):
    def __init__(self, parent, rectangles: list):
        super().__init__(parent, size=(600, 600))

        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.Bind(wx.EVT_PAINT, self.on_paint)
        self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
        self.Bind(wx.EVT_LEFT_UP, self.on_left_up)
        self.Bind(wx.EVT_MOTION, self.on_mouse_move)
        self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)

        self.rect_height = GRID_HEIGHT
        self.rectangles = rectangles
        self.dragging = False
        self.resizing = False
        self.offset = wx.Point(0, 0)
        self.selected_rectangle = None
        self.zoom_factor = 1.0

        self.SetScrollRate(GRID_WIDTH, GRID_HEIGHT)
        self.SetVirtualSize((MAX_LENGTH * GRID_WIDTH, GRID_HEIGHT * 128))

    def get_cursor_pos(self, event) -> tuple:
        scrolled_x = self.GetScrollPos(wx.HORIZONTAL)
        scrolled_y = self.GetScrollPos(wx.VERTICAL)

        pos = event.GetPosition()

        return (
            pos.x / self.zoom_factor + scrolled_x * GRID_WIDTH,
            pos.y / self.zoom_factor + scrolled_y * GRID_HEIGHT,
        )

    def on_paint(self, event):
        dc = wx.AutoBufferedPaintDC(self)
        self.PrepareDC(dc)
        dc.Clear()
        self.draw_grid(dc)
        self.draw_line_numbers(dc)
        self.draw_column_numbers(dc)
        self.draw_rectangles(dc)

        # scroll_x = self.GetScrollPos(wx.HORIZONTAL)
        # scroll_y = self.GetScrollPos(wx.VERTICAL)

        # print(f"Horizontal Scroll Position: {scroll_x}")
        # print(f"Vertical Scroll Position: {scroll_y}")

    def draw_grid(self, dc):
        dc.SetPen(
            wx.Pen(wx.Colour(200, 200, 200), round(self.zoom_factor), wx.PENSTYLE_SOLID)
        )
        width, height = self.GetVirtualSize()
        for x in range(0, round(width / self.zoom_factor + 0.5), GRID_WIDTH):
            dc.DrawLine(int(x * self.zoom_factor), 0, int(x * self.zoom_factor), height)
        for y in range(0, round(height / self.zoom_factor + 0.5), GRID_HEIGHT):
            dc.DrawLine(0, int(y * self.zoom_factor), width, int(y * self.zoom_factor))

    def draw_rectangles(self, dc):
        for rect in self.rectangles:
            dc.SetBrush(wx.Brush(rect.colour))
            dc.SetPen(
                wx.Pen(
                    rect.colour, width=round(self.zoom_factor), style=wx.PENSTYLE_SOLID
                )
            )
            dc.DrawRectangle(
                int(rect.x_pos * self.zoom_factor),
                int(rect.y_pos * self.zoom_factor),
                int(rect.width_length * self.zoom_factor),
                int(self.rect_height * self.zoom_factor),
            )

    def draw_line_numbers(self, dc):
        dc.SetFont(
            wx.Font(
                10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL
            )
        )
        dc.SetTextForeground(wx.Colour(0, 0, 0))
        for i in range(
            0, self.GetVirtualSize()[1] // int(GRID_HEIGHT * self.zoom_factor)
        ):
            dc.DrawText(str(i), 5, round(i * GRID_HEIGHT * self.zoom_factor + 5))

    def draw_column_numbers(self, dc):
        dc.SetFont(
            wx.Font(
                10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL
            )
        )
        dc.SetTextForeground(wx.Colour(0, 0, 0))
        for i in range(
            0, self.GetVirtualSize()[0] // int(GRID_WIDTH * self.zoom_factor)
        ):
            if i % 5 == 0:
                dc.DrawText(str(i), round(i * GRID_WIDTH * self.zoom_factor + 5), 5)

    def on_left_down(self, event):
        pos = self.get_cursor_pos(event)
        for rect in self.rectangles:
            rect_right = rect.x_pos + rect.width_length
            if (
                rect.x_pos <= pos[0] <= rect_right
                and rect.y_pos <= pos[1] <= rect.y_pos + self.rect_height
            ):
                if abs(pos[0] - rect_right) <= BORDER_THRESHOLD:
                    self.resizing = True
                else:
                    self.dragging = True
                    self.offset = wx.Point(
                        round(pos[0] - rect.pos.x),
                        round(pos[1] - rect.pos.y),
                    )
                self.selected_rectangle = rect
                break

    def on_left_up(self, event):
        if self.dragging or self.resizing:
            self.dragging = False
            self.resizing = False
            self.snap_to_grid()
            self.Refresh()
            self.determin_note()

    def on_mouse_move(self, event):
        pos = self.get_cursor_pos(event)
        if event.Dragging() and event.LeftIsDown():
            if self.dragging and self.selected_rectangle:
                self.selected_rectangle.x_pos = pos[0] - self.offset.x
                self.selected_rectangle.y_pos = pos[1] - self.offset.y
                self.apply_snap_effect()
                self.Refresh()
            elif self.resizing and self.selected_rectangle:
                new_width = pos[0] - self.selected_rectangle.x_pos
                if new_width >= GRID_WIDTH:
                    self.selected_rectangle.width_length = new_width
                    snap_w = round(self.selected_rectangle.width) * GRID_WIDTH
                    if (
                        abs(snap_w - self.selected_rectangle.width_length)
                        <= SNAP_THRESHOLD
                    ):
                        self.selected_rectangle.width_length = snap_w
                self.Refresh()
        else:
            cursor = wx.Cursor(wx.CURSOR_ARROW)
            for rect in self.rectangles:
                rect_right = rect.x_pos + rect.width_length
                if (
                    rect.x_pos <= pos[0] <= rect_right
                    and rect.y_pos <= pos[1] <= rect.y_pos + self.rect_height
                ):
                    if abs(pos[0] - rect_right) <= BORDER_THRESHOLD:
                        cursor = wx.Cursor(wx.CURSOR_SIZEWE)
                    else:
                        cursor = wx.Cursor(wx.CURSOR_SIZING)
                    break
            self.SetCursor(cursor)

    def apply_snap_effect(self):
        if not self.selected_rectangle:
            raise ValueError("No rectangle selected")
        snap_x = round(self.selected_rectangle.x * GRID_WIDTH)
        snap_y = round(self.selected_rectangle.y * GRID_HEIGHT)

        dist_x, dist_y = (
            snap_x - self.selected_rectangle.x_pos,
            snap_y - self.selected_rectangle.y_pos,
        )

        if abs(dist_x) <= SNAP_THRESHOLD:
            self.selected_rectangle.x_pos = snap_x
        if abs(dist_y) <= SNAP_THRESHOLD:
            self.selected_rectangle.y_pos = snap_y

    def snap_to_grid(self):
        if not self.selected_rectangle:
            raise ValueError("No rectangle selected")
        snap_x = round(self.selected_rectangle.x) * GRID_WIDTH
        snap_y = round(self.selected_rectangle.y) * GRID_HEIGHT
        self.selected_rectangle.redefine_pos(wx.Point(snap_x, snap_y))

    def determin_note(self):
        if not self.selected_rectangle:
            raise ValueError("No rectangle selected")

        print(
            "The rectangle is on pos ({},{}), with width of {}".format(
                self.selected_rectangle.x,
                self.selected_rectangle.y,
                self.selected_rectangle.width,
            )
        )

    def on_mouse_wheel(self, event):
        if event.ControlDown():
            delta = event.GetWheelRotation()
            if delta > 0:
                self.zoom_factor *= 1.1
            elif delta < 0:
                self.zoom_factor /= 1.1

            self.SetScrollRate(
                round(GRID_WIDTH * self.zoom_factor),
                round(GRID_HEIGHT * self.zoom_factor),
            )
            self.SetVirtualSize(
                (
                    round(MAX_LENGTH * GRID_WIDTH * self.zoom_factor + 0.5),
                    round(GRID_HEIGHT * 128 * self.zoom_factor + 0.5),
                )
            )
            self.Refresh()
        elif event.ShiftDown():
            # Handle horizontal scrolling
            scroll_x = self.GetScrollPos(wx.HORIZONTAL)
            delta_x = (
                event.GetWheelRotation() // event.GetWheelDelta()
            )  # * event.GetLinesPerAction()
            self.Scroll(scroll_x - delta_x, -1)
        else:
            # Handle vertical scrolling
            scroll_y = self.GetScrollPos(wx.VERTICAL)
            delta_y = (
                event.GetWheelRotation() // event.GetWheelDelta()
            )  # * event.GetLinesPerAction()
            self.Scroll(-1, scroll_y - delta_y)


class MainFrame(wx.Frame):
    def __init__(self, rectangles_data: list):
        super().__init__(None, title="Draggable Rectangles", size=(600, 600))
        panel = RectanglePanel(self, rectangles_data)

        # Create a sizer to add the panel to the frame
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(panel, 1, wx.EXPAND)

        self.SetSizerAndFit(sizer)
        self.Show()


if __name__ == "__main__":

    msq = Musicreater.MusicSequence.load_decode(
        open(
            input(
                "MSQ:",
            ),
            "rb",
        ).read()
    )
    colour_map = dict(
        [
            (
                nns,
                (
                    random.randint(0, 255),
                    random.randint(0, 255),
                    random.randint(0, 255),
                ),
            )
            for nns in msq.note_count_per_instrument.keys()
        ]
    )
    app = wx.App(False)
    frame = MainFrame(
        [
            Rectangle.from_note(note, (colour_map[note.sound_name]))
            for note in sorted(
                [i for j in msq.channels.values() for i in j],
                key=lambda note: note.start_tick,
            )
        ]
    )
    app.MainLoop()
